{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "## 检索\n",
    "\n",
    "索引三篇博客文章\n",
    "> 依赖` poetry add beautifulsoup4`"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "5e2590da56eee2f3"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "USER_AGENT environment variable not set, consider setting it to identify your requests.\n"
     ]
    }
   ],
   "source": [
    "from dotenv import load_dotenv\n",
    "import os\n",
    "from langchain_community.embeddings import DashScopeEmbeddings\n",
    "from langchain_community.vectorstores import Chroma\n",
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "from langchain_community.document_loaders import WebBaseLoader\n",
    "\n",
    "urls = [\n",
    "    \"https://lilianweng.github.io/posts/2023-06-23-agent/\",\n",
    "    \"https://lilianweng.github.io/posts/2023-03-15-prompt-engineering/\",\n",
    "    \"https://lilianweng.github.io/posts/2023-10-25-adv-attack-llm/\",\n",
    "]\n",
    "\n",
    "# 使用 WebBaseLoader 从每个 URL 加载内容\n",
    "docs = [WebBaseLoader(url).load() for url in urls]\n",
    "# 将所有文档列表展平为单个列表\n",
    "docs_list = [item for sublist in docs for item in sublist]\n",
    "\n",
    "# 创建文本分块器\n",
    "text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(chunk_size=1000, chunk_overlap=50)\n",
    "\n",
    "doc_splits = text_splitter.split_documents(docs_list)\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "# 增加到 ChromaDB\n",
    "vectorstore = Chroma.from_documents(doc_splits, collection_name=\"rag-chroma\", persist_directory=\"./data/agentic_rag/\",\n",
    "                                    embedding=DashScopeEmbeddings(dashscope_api_key=os.getenv(\"DASHSCOPE_API_KEY\")))\n",
    "\n",
    "# retriever = vectorstore.as_retriever(search_typy=\"bm25\",\n",
    "#                                      search_kwargs={\"k\": 3})"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-15T07:52:09.573144Z",
     "start_time": "2024-11-15T07:48:52.018080Z"
    }
   },
   "id": "96b5bfe874f24cdc",
   "execution_count": 1
  },
  {
   "cell_type": "markdown",
   "source": [
    "**API Reference:** [WebBaseLoader](https://python.langchain.com/api_reference/community/document_loaders/langchain_community.document_loaders.web_base.WebBaseLoader.html) | [Chroma](https://python.langchain.com/api_reference/community/vectorstores/langchain_community.vectorstores.chroma.Chroma.html) | [OpenAIEmbeddings](https://python.langchain.com/api_reference/openai/embeddings/langchain_openai.embeddings.base.OpenAIEmbeddings.html) | [RecursiveCharacterTextSplitter](https://python.langchain.com/api_reference/text_splitters/character/langchain_text_splitters.character.RecursiveCharacterTextSplitter.html)\n",
    "\n",
    "创建检索工具"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "92ac1fa6ca383eab"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/n2/51f_nb3j37g1wvdg9lmmxk200000gn/T/ipykernel_30341/3166230573.py:6: LangChainDeprecationWarning: The class `Chroma` was deprecated in LangChain 0.2.9 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-chroma package and should be used instead. To use it run `pip install -U :class:`~langchain-chroma` and import as `from :class:`~langchain_chroma import Chroma``.\n",
      "  retriever=Chroma(collection_name=\"rag-chroma\", persist_directory=\"./data/agentic_rag\",\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.tools import create_retriever_tool\n",
    "\n",
    "retriever_tool = create_retriever_tool(\n",
    "    name=\"retriever\",\n",
    "    description=\"Search and return information about Lilian Weng blog posts on LLM agents, prompt engineering, and adversarial attacks on LLMs.\",\n",
    "    retriever=Chroma(collection_name=\"rag-chroma\", persist_directory=\"./data/agentic_rag\",\n",
    "                     embedding_function=DashScopeEmbeddings(\n",
    "                         dashscope_api_key=os.getenv(\"DASHSCOPE_API_KEY\"))).as_retriever(search_type=\"mmr\",\n",
    "                                                                                         search_kwargs={\"k\": 3}),\n",
    ")\n",
    "\n",
    "tools = [retriever_tool]"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-15T07:52:09.989142Z",
     "start_time": "2024-11-15T07:52:09.575547Z"
    }
   },
   "id": "1750f1f48e5b0314",
   "execution_count": 2
  },
  {
   "cell_type": "markdown",
   "source": [
    "**API Reference:** [create_retriever_tool](https://python.langchain.com/api_reference/core/tools/langchain_core.tools.retriever.create_retriever_tool.html)\n",
    "\n",
    "## Agent State\n",
    "我们将定义一个graph。 Graph传递给每个节点的状态对象。 我们的状态将是一个消息列表。 图中的每个节点都将对其进行添加。\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "5287493f938f30b6"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "from langgraph.graph import add_messages\n",
    "from langchain_core.messages import BaseMessage\n",
    "from typing import TypedDict, Annotated, Sequence\n",
    "\n",
    "\n",
    "class AgentState(TypedDict):\n",
    "    # add_messages函数定义了应该如何处理更新，默认是替换。add_messages表示“追加”\n",
    "    messages: Annotated[Sequence[BaseMessage], add_messages]"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-15T07:52:10.095417Z",
     "start_time": "2024-11-15T07:52:09.991650Z"
    }
   },
   "id": "a23bfaca8bc90041",
   "execution_count": 3
  },
  {
   "cell_type": "markdown",
   "source": [
    "**API Reference:**![BaseMessage](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.base.BaseMessage.html) | ![add_messages](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages)\n",
    "\n",
    "## Nodes and Edges\n",
    "我们可以这样绘制一个代理RAG图：\n",
    "- 状态是一组消息 \n",
    "- 每个节点都会更新（追加到）状态 \n",
    "- 条件边决定接下来要访问哪个节点\n",
    "\n",
    "![](../../images/rag1.png)\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "16302ff3b7cd84ae"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "---START CHAT---\n",
      "================================\u001B[1m Human Message \u001B[0m=================================\n",
      "\n",
      "You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\n",
      "Question: \u001B[33;1m\u001B[1;3m{question}\u001B[0m \n",
      "Context: \u001B[33;1m\u001B[1;3m{context}\u001B[0m \n",
      "Answer:\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "from langchain_core.runnables import RunnableLambda\n",
    "from langchain import hub\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.messages import HumanMessage\n",
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_openai import ChatOpenAI\n",
    "from pydantic import BaseModel, Field\n",
    "from typing import Literal\n",
    "\n",
    "\n",
    "### Edges 决策\n",
    "\n",
    "def grade_documents(state) -> Literal[\"generate\", \"rewrite\"]:\n",
    "    \"\"\"\n",
    "    决策检索到的文档是否与问题相关。\n",
    "\n",
    "    Args:\n",
    "        state (messages): The current state\n",
    "\n",
    "    Returns:\n",
    "        str: A decision for whether the documents are relevant or not\n",
    "    \"\"\"\n",
    "    print(\"---决策---\")\n",
    "\n",
    "    # Data model\n",
    "    class grade(BaseModel):\n",
    "        \"\"\"关联性检查.\"\"\"\n",
    "        binary_score: str = Field(description=\"Relevance score 'yes' or 'no'\")\n",
    "\n",
    "    #LLM\n",
    "    model = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        model_name=\"qwen-max\",\n",
    "        temperature=0, streaming=True,stream_usage=True,\n",
    "    )\n",
    "\n",
    "    # LLM with tool and validation\n",
    "    llm_with_tool = model.with_structured_output(grade)\n",
    "\n",
    "    prompt = PromptTemplate(\n",
    "        template=\"\"\"You are a grader assessing relevance of a retrieved document to a user question. \\n \n",
    "        Here is the retrieved document: \\n\\n {context} \\n\\n\n",
    "        Here is the user question: {question} \\n\n",
    "        If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \\n\n",
    "        Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.\n",
    "        \"\"\",\n",
    "        input_variables=[\"context\", \"question\"],\n",
    "    )\n",
    "\n",
    "    # chain\n",
    "    chain = prompt | llm_with_tool\n",
    "\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "\n",
    "    question = messages[0].content\n",
    "    doc = last_message.content\n",
    "\n",
    "    # 获取最近一次消息，将问题及回答内容交给决策智能体核对回答是否与问题相关\n",
    "    scored_result = chain.invoke({\"context\": doc, \"question\": question})\n",
    "\n",
    "    # scored_result包含：binary_score='yes' or 'no'\n",
    "    score = scored_result.binary_score\n",
    "\n",
    "    if score == \"yes\":\n",
    "        print(\"---决策: 相关文档---\")\n",
    "        return \"generate\"\n",
    "    else:\n",
    "        print(\"---决策：文档不相关(兜底策略)---\")\n",
    "        print(score)\n",
    "        return \"rewrite\"\n",
    "\n",
    "\n",
    "### Nodes 节点\n",
    "\n",
    "def agent(state):\n",
    "    \"\"\"\n",
    "    调用代理模型以基于当前状态生成响应。鉴于对于这个问题，它将决定使用检索器工具进行检索，或者结束。\n",
    "\n",
    "    Args:\n",
    "        state (messages): The current state\n",
    "\n",
    "    Returns:\n",
    "        dict: The updated state with the agent response appended to messages\n",
    "    \"\"\"\n",
    "    print(\"--- CALL AGENT ---\")\n",
    "    messages = state[\"messages\"]\n",
    "    model = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        temperature=0, streaming=True,\n",
    "        model_name=\"qwen-max\",stream_usage=True,\n",
    "    )\n",
    "    model = model.bind_tools(tools)\n",
    "    response = model.invoke(messages)\n",
    "    print(f\"---AGENT RESPONSE: {response}---\")\n",
    "    # We return a list, because this will get added to the existing list\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "## 将问题重新投递给AI\n",
    "def rewrite(state):\n",
    "    \"\"\"\n",
    "    Transform the query to produce a better question.\n",
    "\n",
    "    Args:\n",
    "        state (messages): The current state\n",
    "\n",
    "    Returns:\n",
    "        dict: The updated state with re-phrased question\n",
    "    \"\"\"\n",
    "    print(\"---TRANSFORM QUERY---\")\n",
    "    messages = state[\"messages\"]\n",
    "    #取出第一个消息获取问题内容\n",
    "    question = messages[0].content\n",
    "    # 重新提问\n",
    "    msg = [HumanMessage(content=f\"\"\"\\n\n",
    "    Look at the input and try to reason about the underlying semantic intent / meaning. \\n \n",
    "    Here is the initial question:\n",
    "    \\n ------- \\n\n",
    "    {question} \n",
    "    \\n ------- \\n\n",
    "    Formulate an improved question: \"\"\",\n",
    "                        )]\n",
    "    # Grader\n",
    "    model = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        temperature=0, streaming=True,\n",
    "        model_name=\"qwen-max\",stream_usage=True,\n",
    "    )\n",
    "    response = model.invoke(msg)\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "def generate(state):\n",
    "    \"\"\"\n",
    "    Generate answer\n",
    "\n",
    "    Args:\n",
    "        state (messages): The current state\n",
    "\n",
    "    Returns:\n",
    "         dict: The updated state with re-phrased question\n",
    "    \"\"\"\n",
    "    print(\"---GENERATE---\")\n",
    "    messages = state[\"messages\"]\n",
    "    question = messages[0].content\n",
    "    last_message = messages[-1]\n",
    "    docs = last_message.content\n",
    "\n",
    "    # Prompt\n",
    "    prompt = hub.pull(\"rlm/rag-prompt\")\n",
    "\n",
    "#     prompt = PromptTemplate(\n",
    "#         template=\"\"\"\n",
    "#         ================================ Human Message =================================\n",
    "# \n",
    "# You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\n",
    "# Question: {question} \n",
    "# Context: {context} \n",
    "# Answer:\n",
    "#         \"\"\",\n",
    "#         input_variables=[\"context\", \"question\"],\n",
    "#     )\n",
    "\n",
    "    llm = ChatOpenAI(\n",
    "        # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "        openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "        openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "        temperature=0, streaming=True,\n",
    "        model_name=\"qwen-max\",stream_usage=True,\n",
    "    )\n",
    "\n",
    "    # Post-processing\n",
    "    def format_docs(docs):\n",
    "        # for doc in docs:\n",
    "        #     if not isinstance(doc, str):\n",
    "        #         print(f\"文档格式:{doc}\")\n",
    "\n",
    "        return \"\\n\\n\".join(doc for doc in docs)\n",
    "\n",
    "    # chain\n",
    "    rag_chain = prompt | llm | StrOutputParser()\n",
    "\n",
    "    #Run\n",
    "    response = rag_chain.invoke({\"context\": format_docs(docs), \"question\": question})\n",
    "\n",
    "    return {\"messages\": [response]}\n",
    "\n",
    "\n",
    "print(\"---START CHAT---\")\n",
    "prompt = hub.pull(\"rlm/rag-prompt\").pretty_print()  # Show what the prompt looks like\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-15T07:52:12.388848Z",
     "start_time": "2024-11-15T07:52:10.097523Z"
    }
   },
   "id": "88e59293d34b701",
   "execution_count": 4
  },
  {
   "cell_type": "markdown",
   "source": [
    "**API Reference:**[BaseMessage](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.base.BaseMessage.html)[HumanMessage](https://python.langchain.com/api_reference/core/messages/langchain_core.messages.human.HumanMessage.html)[StrOutputParser](https://python.langchain.com/api_reference/core/output_parsers/langchain_core.output_parsers.string.StrOutputParser.html)[PromptTemplate](https://python.langchain.com/api_reference/core/prompts/langchain_core.prompts.prompt.PromptTemplate.html)[ChatOpenAI](https://python.langchain.com/api_reference/openai/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html)[tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.tool_node.tools_condition)\n",
    "\n",
    "## Graph\n",
    "- `call_model`\n",
    "- Agent make a decision to call a function\n",
    "- If so, then`action`to call tool (retriever)\n",
    "- Then call agent with the tool output added to messages (`state`)"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "2220485144bad16a"
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.constants import START, END\n",
    "from langgraph.prebuilt import ToolNode, tools_condition\n",
    "from langgraph.graph import StateGraph\n",
    "\n",
    "# Define a new graph\n",
    "workflow = StateGraph(AgentState)\n",
    "\n",
    "# Define the nodes we will cycle between\n",
    "workflow.add_node(\"agent\", agent)\n",
    "retrieve = ToolNode([retriever_tool])\n",
    "workflow.add_node(\"retrieve\", retrieve)  # retrieval\n",
    "workflow.add_node(\"rewrite\", rewrite)  # Re-writing the question\n",
    "workflow.add_node(\"generate\", generate)  # Generating a response after we know the documents are relevant\n",
    "# Call agent node to decide to retrieve or not\n",
    "workflow.add_edge(START, \"agent\")\n",
    "\n",
    "# Decide whether to retrieve\n",
    "workflow.add_conditional_edges(\"agent\",\n",
    "                               # Assess agent decision\n",
    "                               tools_condition, {\n",
    "                                   # Translate the condition outputs to nodes in our graph\n",
    "                                   \"tools\": \"retrieve\",\n",
    "                                   END: END,\n",
    "                               })\n",
    "\n",
    "# Edges taken after the `action` node is called.\n",
    "workflow.add_conditional_edges(\"retrieve\",\n",
    "                               # Assess agent decision\n",
    "                               grade_documents)\n",
    "workflow.add_edge(\"generate\", END)\n",
    "workflow.add_edge(\"rewrite\", \"agent\")\n",
    "memory = MemorySaver()\n",
    "\n",
    "# Compile\n",
    "graph = workflow.compile(checkpointer=memory)\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-15T07:52:12.419264Z",
     "start_time": "2024-11-15T07:52:12.392620Z"
    }
   },
   "id": "4ef2aa6233c858d9",
   "execution_count": 5
  },
  {
   "cell_type": "markdown",
   "source": [
    "**API Reference**: END | StateGraph | START | ToolNode\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "c0d5dfb0d5426d5"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": "<IPython.core.display.Image object>"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "try:\n",
    "    display(Image(graph.get_graph(xray=True).draw_mermaid_png()))\n",
    "except Exception:\n",
    "    # This requires some extra dependencies and is optional\n",
    "    pass"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-15T07:52:13.325071Z",
     "start_time": "2024-11-15T07:52:12.420986Z"
    }
   },
   "id": "9a7d1c130969bd44",
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "Checkpointer requires one or more of the following 'configurable' keys: ['thread_id', 'checkpoint_ns', 'checkpoint_id']",
     "output_type": "error",
     "traceback": [
      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[0;31mValueError\u001B[0m                                Traceback (most recent call last)",
      "Cell \u001B[0;32mIn[7], line 14\u001B[0m\n\u001B[1;32m     10\u001B[0m config \u001B[38;5;241m=\u001B[39m {\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mconfigurable\u001B[39m\u001B[38;5;124m\"\u001B[39m: {\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mthread_id\u001B[39m\u001B[38;5;124m\"\u001B[39m: \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m3\u001B[39m\u001B[38;5;124m\"\u001B[39m, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124muser_id\u001B[39m\u001B[38;5;124m\"\u001B[39m: \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m2\u001B[39m\u001B[38;5;124m\"\u001B[39m}}\n\u001B[1;32m     11\u001B[0m \u001B[38;5;66;03m# graph.invoke(inputs,config)\u001B[39;00m\n\u001B[1;32m     12\u001B[0m \u001B[38;5;66;03m# for m in graph.get_state(config).values[\"messages\"]:\u001B[39;00m\n\u001B[1;32m     13\u001B[0m \u001B[38;5;66;03m#     pprint.pprint(f\"m:{m}\")\u001B[39;00m\n\u001B[0;32m---> 14\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m output \u001B[38;5;129;01min\u001B[39;00m graph\u001B[38;5;241m.\u001B[39mstream(inputs):\n\u001B[1;32m     15\u001B[0m     \u001B[38;5;28;01mfor\u001B[39;00m key, value \u001B[38;5;129;01min\u001B[39;00m output\u001B[38;5;241m.\u001B[39mitems():\n\u001B[1;32m     16\u001B[0m         pprint\u001B[38;5;241m.\u001B[39mpprint(\u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mOutput from node \u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mkey\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m:\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langgraph/pregel/__init__.py:1258\u001B[0m, in \u001B[0;36mPregel.stream\u001B[0;34m(self, input, config, stream_mode, output_keys, interrupt_before, interrupt_after, debug, subgraphs)\u001B[0m\n\u001B[1;32m   1242\u001B[0m run_manager \u001B[38;5;241m=\u001B[39m callback_manager\u001B[38;5;241m.\u001B[39mon_chain_start(\n\u001B[1;32m   1243\u001B[0m     \u001B[38;5;28;01mNone\u001B[39;00m,\n\u001B[1;32m   1244\u001B[0m     \u001B[38;5;28minput\u001B[39m,\n\u001B[1;32m   1245\u001B[0m     name\u001B[38;5;241m=\u001B[39mconfig\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrun_name\u001B[39m\u001B[38;5;124m\"\u001B[39m, \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mget_name()),\n\u001B[1;32m   1246\u001B[0m     run_id\u001B[38;5;241m=\u001B[39mconfig\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mrun_id\u001B[39m\u001B[38;5;124m\"\u001B[39m),\n\u001B[1;32m   1247\u001B[0m )\n\u001B[1;32m   1248\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[1;32m   1249\u001B[0m     \u001B[38;5;66;03m# assign defaults\u001B[39;00m\n\u001B[1;32m   1250\u001B[0m     (\n\u001B[1;32m   1251\u001B[0m         debug,\n\u001B[1;32m   1252\u001B[0m         stream_modes,\n\u001B[1;32m   1253\u001B[0m         output_keys,\n\u001B[1;32m   1254\u001B[0m         interrupt_before_,\n\u001B[1;32m   1255\u001B[0m         interrupt_after_,\n\u001B[1;32m   1256\u001B[0m         checkpointer,\n\u001B[1;32m   1257\u001B[0m         store,\n\u001B[0;32m-> 1258\u001B[0m     ) \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_defaults(\n\u001B[1;32m   1259\u001B[0m         config,\n\u001B[1;32m   1260\u001B[0m         stream_mode\u001B[38;5;241m=\u001B[39mstream_mode,\n\u001B[1;32m   1261\u001B[0m         output_keys\u001B[38;5;241m=\u001B[39moutput_keys,\n\u001B[1;32m   1262\u001B[0m         interrupt_before\u001B[38;5;241m=\u001B[39minterrupt_before,\n\u001B[1;32m   1263\u001B[0m         interrupt_after\u001B[38;5;241m=\u001B[39minterrupt_after,\n\u001B[1;32m   1264\u001B[0m         debug\u001B[38;5;241m=\u001B[39mdebug,\n\u001B[1;32m   1265\u001B[0m     )\n\u001B[1;32m   1266\u001B[0m     \u001B[38;5;66;03m# set up messages stream mode\u001B[39;00m\n\u001B[1;32m   1267\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mmessages\u001B[39m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;129;01min\u001B[39;00m stream_modes:\n",
      "File \u001B[0;32m/opt/anaconda3/envs/ai_312/lib/python3.12/site-packages/langgraph/pregel/__init__.py:1126\u001B[0m, in \u001B[0;36mPregel._defaults\u001B[0;34m(self, config, stream_mode, output_keys, interrupt_before, interrupt_after, debug)\u001B[0m\n\u001B[1;32m   1124\u001B[0m     checkpointer \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcheckpointer\n\u001B[1;32m   1125\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m checkpointer \u001B[38;5;129;01mand\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m config\u001B[38;5;241m.\u001B[39mget(CONF):\n\u001B[0;32m-> 1126\u001B[0m     \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\n\u001B[1;32m   1127\u001B[0m         \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mCheckpointer requires one or more of the following \u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mconfigurable\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m keys: \u001B[39m\u001B[38;5;132;01m{\u001B[39;00m[s\u001B[38;5;241m.\u001B[39mid\u001B[38;5;250m \u001B[39m\u001B[38;5;28;01mfor\u001B[39;00m\u001B[38;5;250m \u001B[39ms\u001B[38;5;250m \u001B[39m\u001B[38;5;129;01min\u001B[39;00m\u001B[38;5;250m \u001B[39mcheckpointer\u001B[38;5;241m.\u001B[39mconfig_specs]\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m\"\u001B[39m\n\u001B[1;32m   1128\u001B[0m     )\n\u001B[1;32m   1129\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m CONFIG_KEY_STORE \u001B[38;5;129;01min\u001B[39;00m config\u001B[38;5;241m.\u001B[39mget(CONF, {}):\n\u001B[1;32m   1130\u001B[0m     store: Optional[BaseStore] \u001B[38;5;241m=\u001B[39m config[CONF][CONFIG_KEY_STORE]\n",
      "\u001B[0;31mValueError\u001B[0m: Checkpointer requires one or more of the following 'configurable' keys: ['thread_id', 'checkpoint_ns', 'checkpoint_id']"
     ]
    }
   ],
   "source": [
    "import pprint\n",
    "\n",
    "inputs = {\n",
    "    \"messages\": [\n",
    "        (\"user\", \"What does Lilian Weng say about the types of agent memory?\"),\n",
    "    ]\n",
    "}\n",
    "\n",
    "\n",
    "config = {\"configurable\": {\"thread_id\": \"3\", \"user_id\": \"2\"}}\n",
    "# graph.invoke(inputs,config)\n",
    "# for m in graph.get_state(config).values[\"messages\"]:\n",
    "#     pprint.pprint(f\"m:{m}\")\n",
    "for output in graph.stream(inputs):\n",
    "    for key, value in output.items():\n",
    "        pprint.pprint(f\"Output from node '{key}':\")\n",
    "        pprint.pprint(\"---\")\n",
    "        # pprint.pprint(value, indent=2, width=80, depth=None)\n",
    "    pprint.pprint(\"\\n---\\n\")"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-15T07:52:14.374509Z",
     "start_time": "2024-11-15T07:52:13.328222Z"
    }
   },
   "id": "dd90b500b9da67f8",
   "execution_count": 7
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
